package edu.stonybrook.cs.gars.server.data;

import java.io.*;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.StringTokenizer;

import edu.stonybrook.cs.gars.server.data.Rules.Rule;
import edu.stonybrook.cs.gars.server.data.exceptions.*;

/**
 * @author Tony Arra Parses and contains information from a tab-separated data
 *         file Available file types: ApplyYourself; Official Test Scores Note:
 */
public class Data {
	private ArrayList<String> fields = new ArrayList<String>();
	private ArrayList<ArrayList<String>> records = new ArrayList<ArrayList<String>>();

	/**
	 * Creates a parsed Data object
	 * 
	 * @param file
	 */
	public Data(File file) throws UploadException {
		try {
			parse(file);
		} catch (FileNotFoundException e) {
			throw new UploadException(e.getMessage());
		} catch (EmptyFileException e) {
			throw e;
		} catch (IOException e) {
			throw new UploadException(e.getMessage());
		}
	}

	/**
	 * Parses data from the file
	 * 
	 * @throws IOException
	 */
	private void parse(File file) throws EmptyFileException, IOException {
		String line;
		BufferedReader reader = new BufferedReader(new FileReader(file));
		ArrayList<String> record;

		// Read fields
		if ((line = reader.readLine()) != null && line.length() > 0) {
			readRecord(line, fields);
		} else {
			throw new EmptyFileException(file);
		}

		// Read records
		while ((line = reader.readLine()) != null && line.length() > 0) {
			record = new ArrayList<String>();
			readRecord(line, record);
			records.add(record);
		}
		if (records.isEmpty()) {
			throw new EmptyFileException(file.getName() + "has no records");
		}
	}

	/**
	 * Verifies the validity of a data file
	 * 
	 * @return true if valid; false otherwise
	 * @param rules
	 * @throws UploadException
	 */
	public void verify(Rules rules) throws UploadException {
		Iterator<Rule> rulesIt = rules.getRulesIt();
		Iterator<String> fieldsIt = this.getFieldsIt();
		String field1, field2;
		while (fieldsIt.hasNext()) {
			field1 = removeQuotes(fieldsIt.next());
			//Missing mapping
			if (rulesIt.hasNext()) {
				field2 = removeQuotes(rulesIt.next().getFrom());
			} else
				throw new NoMappingException(field1);
			//Field match error
			if (!field1.equals(field2)) {
				throw new FieldMatchException(field1, field2);
			}
		}
		//Check for duplicate records
		Iterator<ArrayList<String>> recordsIt = this.getRecordsIt();
		ArrayList<String> record;
		int lastIndex = records.size();
		for (int i = 0; recordsIt.hasNext(); i++) {
			record = recordsIt.next();
			if (i < lastIndex && records.subList(i + 1, lastIndex).contains(record)) {
				throw new DuplicateRecordException(record);
			}
		}

		checkFieldCount();	
	}
	/**
	 * Check that records have same number of fields as header line
	 * @throws RecordSizeException 
	 */
	private void checkFieldCount() throws RecordSizeException {
		Iterator<ArrayList<String>> recordsIt = this.getRecordsIt();
		ArrayList<String> record;
		int numFields = fields.size();
		while (recordsIt.hasNext()) {
			record = recordsIt.next();
			if (record.size() != numFields) {
				throw new RecordSizeException(record);
			}
		}
	}
	/**
	 * Remove quotation marks from a string
	 * 
	 * @param string
	 * @return string without the quotation marks
	 */
	private String removeQuotes(String string) {
		int i, j;
		i = string.indexOf("\"") + 1;
		j = string.lastIndexOf("\"");
		if (i != -1 && j != -1) {
			return string.substring(i, j);
		} else
			return string;
	}

	/**
	 * Reads an individual record from the file. Empty cells in the record are
	 * represented by a null element in the ArrayList.
	 * 
	 * @param line
	 *            line in the file containing data record
	 * @param record
	 *            list of data cells for this line of the file
	 */
	private void readRecord(String line, ArrayList<String> record) {
		StringTokenizer tokenizer = new StringTokenizer(line, "\t", true);
		String token;
		boolean emptyCell = true;
		while (tokenizer.hasMoreTokens()) {
			token = tokenizer.nextToken();
			// Parse record by tab token
			if (token.equals("\t")) {
				// Check for empty cells
				if (emptyCell) {
					// Add null record for empty cell
					record.add(null);
				} else
					emptyCell = true;
				//Append empty token to lists that end in "\t"
				if (!tokenizer.hasMoreTokens()) {
					record.add(null);
				}
			} else {
				// Add token to record
				record.add(token);
				emptyCell = false;
			}

		}
	}

	/**
	 * Returns an iterator for the fields
	 * 
	 * @return field iterator
	 */
	public Iterator<String> getFieldsIt() {
		return fields.iterator();
	}

	/**
	 * Returns an iterator for the records
	 * 
	 * @return record iterator
	 */
	public Iterator<ArrayList<String>> getRecordsIt() {
		return records.iterator();
	}

}
